export default class SerializeForm {
    private form:Array<Object>;
    static get BLACK_LIST() {
        return {
            elements: ['button', 'file', 'reset', 'submit']
        };
    }

    /**
     * Creates an instance of SerializeForm.
     *
     * @constructor
     * @this {SerializeForm}
     * @param {HTMLFormElement} form The form element to seriaize
     */
    constructor(form:HTMLFormElement) {
        this.form = SerializeForm.serialize(form);
    }

    /**
     * Get the form serialization data by format
     *
     * @param {String} dataType - The data type to return ('array', 'object' or null to default string)
     * @returns {(String|Array|Object)}
     */
    getData(dataType:string) {
        switch (dataType) {
            case 'array':
                return SerializeForm.getFormDataArray(this.form);
            case 'object':
                return SerializeForm.getFormDataObject(this.form);
            default:
                return this.form.join('&').replace(/%20/g, '+');
        }
    }

    /**
     * Get the form serialization data string
     *
     * @returns {String}
     */
    toString() {
        return this.form.join('&').replace(/%20/g, '+');
    }

    /**
     * Get the form serialization data array
     *
     * @returns {Array}
     */
    toArray() {
        return SerializeForm.getFormDataArray(this.form);
    }

    /**
     * Get the form serialization data object
     *
     * @returns {Object}
     */
    toObject() {
        return SerializeForm.getFormDataObject(this.form);
    }

    /**
     *
     * @param {HTMLFormElement} form
     * @return {Array<Object>}
     */
    static getFormDataArray(form:Array<Object>) {
        return [
            ...form.map((data:string) => {
                const tempData = data.split('=');
                return {
                    name: tempData[0],
                    value: tempData[1]
                };
            })
        ];
    }

    /**
     *
     * @param {HTMLFormElement} form
     */
    static getFormDataObject(form:Array<Object>) {
        const formData:any = {};
        form.forEach((data:string) => {
            const tempData = data.split('=');
            if (formData[tempData[0]]) {
                const newArrayString = `${typeof formData[tempData[0]] === 'object' ? formData[tempData[0]].join('&') : formData[tempData[0]]}&${tempData[1]}`;
                formData[tempData[0]] = newArrayString.split('&');
            } else {
                formData[tempData[0]] = tempData[1];
            }
        });
        return formData;
    }

    /**
     *
     * @param {HTMLFormElement} form
     * @return {boolean}
     */
    static isValidForm(form:HTMLFormElement) {
        return typeof form === 'object' && form.nodeName === 'FORM';
    }

    /**
     *
     * @param {HTMLElement} el
     * @return {boolean}
     */
    static isvalidElement(el:any) {
        return el.name && !el.disabled && !SerializeForm.BLACK_LIST.elements.includes(el.type);
    }

    /**
     *
     * @param {HTMLFormElement} form
     * @return {Array<Object>}
     */
    static serialize(form:HTMLFormElement) {
        const data:Array<string> = [];
        if (SerializeForm.isValidForm(form)) {
            for(let i=0;i<form.elements.length;i++){
                const el:any=form.elements[i];
                if (SerializeForm.isvalidElement(el)) {
                    if ((el.type !== 'checkbox' && el.type !== 'radio' && el.type !== 'select-multiple'&&el.value) || el.checked) {
                        data.push(`${el.name}=${el.value}`);
                    } else if (el.type === 'select-multiple') {
                        const elementOptions = Array.from(el.options);
                        elementOptions.forEach((opt:any) => {
                            if (opt.selected) data.push(`${el.name}=${opt.value}`);
                        });
                    }
                }
            }
        }
        return data;
    }
}
